﻿using System;
using System.Collections.Generic;
using MugenInjection.Bindings.Builders;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using MugenInjection.Scope;
using MugenInjection.Xml.Interface;

namespace MugenInjection.Xml.Infrastructure
{
    /// <summary>
    /// Represents class for create binding from xml.
    /// </summary>
    public class XmlBindingBuilder : IXmlBindingBuilder
    {
        #region Fields

        private readonly IXmlElement _xmlBinding;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="XmlBindingBuilder"/> class.
        /// </summary>
        public XmlBindingBuilder(IXmlElement xmlBinding)
        {
            Validate.ArgumentNotNull(xmlBinding, "xmlBinding");
            _xmlBinding = xmlBinding;
        }

        #endregion

        #region Implementation of IXmlBindingBuilder

        /// <summary>
        /// Builds an instances of <see cref="IBinding"/> from xml.
        /// </summary>
        /// <param name="injector">The specified <see cref="IInjector"/> for create binding.</param>
        /// <returns>An instances of <see cref="IBinding"/>.</returns>
        public IBinding Build(IInjector injector)
        {
            var listFromTypes = new List<Type>();
            //From
            foreach (var typeXml in _xmlBinding.GetNode(XmlLoaderUtils.FromElementName).GetNodes(XmlLoaderUtils.FromTypeElementName))
                listFromTypes.Add(XmlLoaderUtils.ParseType(typeXml.GetAttribute(XmlLoaderUtils.TypeValueAttr)));
            //Binding type
            var bindingTypeEnum = XmlLoaderUtils.ParseEnum<BindingTypeEnum>(_xmlBinding.GetAttribute(XmlLoaderUtils.BindingTypeAttr));
            var bindingBuilder = new BindingBuilder<object, object, object, object>(listFromTypes, injector, true);
            //To
            string attribute = _xmlBinding.GetAttribute(XmlLoaderUtils.BindingToAttr);
            //Scope
            var scope = XmlLoaderUtils.GetScope(injector, _xmlBinding.GetAttribute(XmlLoaderUtils.BindingScopeAttr));
            //Priority
            BindingPriority? bindingPriority = XmlLoaderUtils.GetPriority(_xmlBinding.GetAttribute(XmlLoaderUtils.BindingPriorityAttr));
            //tryDisposeObjects
            bool tryDisposeObject = XmlLoaderUtils.GetTryDisposeObject(_xmlBinding.GetAttribute(XmlLoaderUtils.BindingTryDisposeAttr));
            switch (bindingTypeEnum)
            {
                case BindingTypeEnum.Type:
                    bindingBuilder.To(XmlLoaderUtils.ParseType(attribute));
                    bindingBuilder.UseCustomActivator(XmlLoaderUtils.GetActivator(_xmlBinding.GetAttribute(XmlLoaderUtils.BindingActivatorAttr), injector.Settings.DefaultActivatorFactory));
                    bindingBuilder.UseCustomConstructorResolver(XmlLoaderUtils.GetConstructorResolver(injector, _xmlBinding.GetAttribute(XmlLoaderUtils.BindingConstructorResolverAttr)));
                    break;
                case BindingTypeEnum.Method:
                    bindingBuilder.ToMethod(XmlLoaderUtils.GetToMethodValue(attribute));
                    //Activator
                    var activator = XmlLoaderUtils.GetActivator(_xmlBinding.GetAttribute(XmlLoaderUtils.BindingActivatorAttr), null);
                    if (activator != null)
                        bindingBuilder.UseCustomActivator(activator);
                    break;
                case BindingTypeEnum.Const:
                    bindingBuilder.ToConstant(attribute);
                    break;
                default:
                    throw Validate.EnumOutOfRange(bindingTypeEnum, "bindingTypeEnum");
            }
            bindingBuilder.InScope(scope());
            if (tryDisposeObject)
                bindingBuilder.TryDisposeObjects();
            if (bindingPriority.HasValue)
                bindingBuilder.SetPriority(bindingPriority.Value);
            string s = _xmlBinding.GetAttribute(XmlLoaderUtils.BindingNamedAttr);
            if (!string.IsNullOrEmpty(s))
                bindingBuilder.NamedBinding(s);

            var whenTypeSt = _xmlBinding.GetAttribute(XmlLoaderUtils.BindingWhenTypeAttr);
            if (!string.IsNullOrEmpty(whenTypeSt))
            {
                var whenTypeEnum = XmlLoaderUtils.ParseEnum<WhenTypeEnum>(whenTypeSt);
                var whenValueSt = _xmlBinding.GetAttribute(XmlLoaderUtils.BindingWhenValueAttr);
                if (string.IsNullOrEmpty(whenValueSt))
                    throw new ArgumentException();
                switch (whenTypeEnum)
                {
                    case WhenTypeEnum.When:
                        bindingBuilder.When(XmlLoaderUtils.GetCanResolveMethod(whenValueSt));
                        break;
                    case WhenTypeEnum.WhenInto:
                        bindingBuilder.WhenInto(XmlLoaderUtils.ParseType(whenValueSt));
                        break;
                    case WhenTypeEnum.WhenIntoIsAssignable:
                        bindingBuilder.WhenIntoIsAssignable(XmlLoaderUtils.ParseType(whenValueSt));
                        break;
                    case WhenTypeEnum.WhenNamespaceEqual:
                        bindingBuilder.WhenNamespaceEqual(whenValueSt);
                        break;
                    case WhenTypeEnum.WhenClassHasAttribute:
                        bindingBuilder.WhenClassHasAttribute(XmlLoaderUtils.ParseType(whenValueSt));
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }

            var element = _xmlBinding.GetNode(XmlLoaderUtils.ParametersElementName);
            if (element != null)
            {
                foreach (var xmlElement in element.GetNodes(XmlLoaderUtils.ParameterElementName))
                {
                    var parameterTypeEnum = XmlLoaderUtils.ParseEnum<ParameterTypeEnum>(xmlElement.GetAttribute(XmlLoaderUtils.ParameterTypeAttr));
                    switch (parameterTypeEnum)
                    {
                        case ParameterTypeEnum.Constructor:
                            bindingBuilder.WithConstructorArgument(
                                xmlElement.GetAttribute(XmlLoaderUtils.ParameterNameAttr),
                                xmlElement.GetAttribute(XmlLoaderUtils.ParameterValueAttr));
                            break;
                        case ParameterTypeEnum.Method:
                            bindingBuilder.WithMethodArgument(xmlElement.GetAttribute(XmlLoaderUtils.ParameterMethodNameAttr),
                               xmlElement.GetAttribute(XmlLoaderUtils.ParameterNameAttr),
                               xmlElement.GetAttribute(XmlLoaderUtils.ParameterValueAttr));
                            break;
                        case ParameterTypeEnum.Property:
                            bindingBuilder.WithPropertyValue(
                                xmlElement.GetAttribute(XmlLoaderUtils.ParameterNameAttr),
                                xmlElement.GetAttribute(XmlLoaderUtils.ParameterValueAttr));
                            break;                        
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }
            }
            element = _xmlBinding.GetNode(XmlLoaderUtils.SettingsElementName);
            if (element != null)
            {
                foreach (var xmlElement in element.GetNodes(XmlLoaderUtils.SettingElementName))
                    bindingBuilder.WithSetting(xmlElement.GetAttribute(XmlLoaderUtils.SettingKeyAttr),
                                               xmlElement.GetAttribute(XmlLoaderUtils.SettingValueAttr));
            }
            return bindingBuilder.Build();
        }

        #endregion
    }
}